package com.baichen;

import java.lang.reflect.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class SqlSessionFactory {

    @SuppressWarnings("all")
    public <T> T getMapper(Class<T> mapperClass) {
        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperClass}, new MapperInvocationHandler());
    }

    static class MapperInvocationHandler implements InvocationHandler {

        private static final String URL = "jdbc:mysql://localhost:3306/mini-mybatis";
        private static final String USERNAME = "root";
        private static final String PASSWORD = "123456";


        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 代理类的所有方法调用都会进入这里
            if (method.getName().startsWith("get") || method.getName().startsWith("select")) {
                return invokeSelect(proxy, method, args);
            }
            return null;
        }

        private Object invokeSelect(Object proxy, Method method, Object[] args) {
            String sql = createSql(method);
            System.out.println(sql);

            try(Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
                PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
                for (int i = 0; i < args.length; i++) {
                    Object arg = args[i];
                    if (arg == null) {
                        preparedStatement.setNull(i + 1, java.sql.Types.NULL);
                    } else if (arg instanceof Integer) {
                        preparedStatement.setInt(i + 1, (Integer) arg);
                    } else if (arg instanceof String) {
                        preparedStatement.setString(i + 1, (String) arg);
                    } else {
                        throw new IllegalArgumentException("Unsupported parameter type: " + arg.getClass());
                    }
                }
                
                try(ResultSet resultSet = preparedStatement.executeQuery()) {
                    if (List.class.isAssignableFrom(method.getReturnType())) {
                        Type genericReturnType = method.getGenericReturnType();
                        ParameterizedType parameterizedType = (ParameterizedType) genericReturnType;
                        Class<?> actualTypeArgument = (Class<?>) parameterizedType.getActualTypeArguments()[0];
                        List<Object> results = new ArrayList<>();
                        while (resultSet.next()) {
                            results.add(parseResult(resultSet, actualTypeArgument));
                        }
                        return results;
                    } else {
                        if (resultSet.next()) {
                            return parseResult(resultSet, method.getReturnType());
                        }
                    }
                }
                
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        private String createSql(Method method) {
            StringBuilder sb = new StringBuilder();
            Class<?> returnType = getEntityType(method);
//            System.out.println("Return type: " + returnType.getName());
            Field[] declaredField = returnType.getDeclaredFields();

            sb.append("SELECT ");
            sb.append(getCols(declaredField));
            sb.append(" FROM ");
            sb.append(getTableName(returnType));
            sb.append(" WHERE ");
            sb.append(getWheres(method));

            return sb.toString();
        }

        private Class<?> getEntityType(Method method) {
            if (List.class.isAssignableFrom(method.getReturnType())) {
                Type genericReturnType = method.getGenericReturnType();
                ParameterizedType parameterizedType = (ParameterizedType) genericReturnType;
                return (Class<?>) parameterizedType.getActualTypeArguments()[0];
            } else {
                return method.getReturnType();
            }
        }

        private static String getCols(Field[] declaredField) {
            return Arrays.stream(declaredField).map(Field::getName).collect(Collectors.joining(","));
        }

        private static String getTableName(Class<?> entityType) {
            if (entityType.getAnnotation(Table.class) != null) {
                return entityType.getAnnotation(Table.class).tableName();
            }
            throw new IllegalArgumentException("Entity type must be annotated with @Table");
        }

        private static String getWheres(Method method) {
            return Arrays.stream(method.getParameters())
                    .map(param -> {
                        Param paramAnnotation = param.getAnnotation(Param.class);
                        if (paramAnnotation == null) {
                            throw new IllegalArgumentException("Parameter must be annotated with @Param");
                        }
                        return paramAnnotation.value() + " = ?";
                    })
                    .collect(Collectors.joining(" AND "));
        }

        private Object parseResult(ResultSet resultSet, Class<?> returnType) {
            try {
                Object instance = returnType.getDeclaredConstructor().newInstance();
                Field[] fields = returnType.getDeclaredFields();
                for (Field field : fields) {
                    field.setAccessible(true);
                    if (field.getType() == Integer.class || field.getType() == int.class) {
                        int value = resultSet.getInt(field.getName());
                        if (resultSet.wasNull() && field.getType() == Integer.class) {
                            field.set(instance, null);
                        } else {
                            field.set(instance, value);
                        }
                    } else if (field.getType() == String.class) {
                        field.set(instance, resultSet.getString(field.getName()));
                    } else {
                        throw new IllegalArgumentException("Unsupported field type: " + field.getType());
                    }
                }
                return instance;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }

    }
}
